﻿using System;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using MyCRM.Infrastructure;
using MyCRM.Infrastructure.Messaging;
using StructureMap;
using StructureMap.Graph;

namespace MyCRM.WebApplication.Bootstrapping
{
    class BootStrapper
    {
        public static void ConfigureApplication()
        {
            RegisterDependencies();
            AutoRegisterCommandExecutors();
            AutoRegisterEventHandlers();
        }

        static void RegisterDependencies()
        {
            IContainer registryContainer = new Container();

            registryContainer.Configure(
                  x =>
                  {
                      x.AddRegistry(new MessageBusRegistry());
                      x.AddRegistry(new StoringRegistry());
                      x.Scan(RegisterMessagingContracts);
                  });

            ServiceLocator.RegisterContainer(registryContainer);
        }

        static void RegisterMessagingContracts(
            IAssemblyScanner scanner)
        {
            var assemblies = new[] { "MyCRM.Domain", "MyCRM.ReadModel" };

            scanner.TheCallingAssembly();
            assemblies.Run(scanner.Assembly);
            scanner.WithDefaultConventions();
            scanner.AddAllTypesOf<DomainCommand>();
            scanner.AddAllTypesOf<DomainEvent>();
            scanner.ConnectImplementationsToTypesClosing(typeof(ICommandHandler<>));
            scanner.ConnectImplementationsToTypesClosing(typeof(IEventHandler<>));
        }

        static void AutoRegisterCommandExecutors()
        {
            var domainCommands = ServiceLocator.GetAllInstances(typeof(DomainCommand));
            var commandMessageBus = ServiceLocator.GetInstance<IMessageBus>();

            domainCommands.Run(domainCommand =>
            {
                var commandType = domainCommand.GetType();
                var closedCommandExecutorType = typeof(ICommandHandler<>).MakeGenericType(commandType);

                try
                {
                    var closedDomainCommandExecutorInstance = ServiceLocator.GetInstance(closedCommandExecutorType);

                    var actionType = Expression.GetActionType(commandType);
                    var executeMethod = closedDomainCommandExecutorInstance.GetType().GetMethod("Execute", new Type[] { commandType });
                    var executeActionDelegate = Delegate.CreateDelegate(actionType, closedDomainCommandExecutorInstance, executeMethod);

                    var registerMethod = commandMessageBus.GetType().GetMethod("Register", BindingFlags.Instance | BindingFlags.Public);
                    registerMethod = registerMethod.MakeGenericMethod(commandType);

                    registerMethod.Invoke(commandMessageBus, new object[] { executeActionDelegate });
                }
                catch (Exception e)
                {
                    Trace.WriteLine(e.Message);
                }
            });
        }

        static void AutoRegisterEventHandlers()
        {
            var domainEvents = ServiceLocator.GetAllInstances(typeof(DomainEvent));
            var eventMessageBus = ServiceLocator.GetInstance<IMessageBus>();

            domainEvents.Run(domainEvent =>
            {
                var eventType = domainEvent.GetType();
                var closedDomainEventHandlerType = typeof(IEventHandler<>).MakeGenericType(eventType);

                try
                {
                    var closedDomainEventHandlerInstance = ServiceLocator.GetInstance(closedDomainEventHandlerType);

                    var actionType = Expression.GetActionType(eventType);
                    var handleMethod = closedDomainEventHandlerInstance.GetType().GetMethod("Handle", new Type[] { eventType });
                    var executeActionDelegate = Delegate.CreateDelegate(actionType, closedDomainEventHandlerInstance, handleMethod);

                    var registerMethod = eventMessageBus.GetType().GetMethod("Register", BindingFlags.Instance | BindingFlags.Public);
                    registerMethod = registerMethod.MakeGenericMethod(eventType);

                    registerMethod.Invoke(eventMessageBus, new object[] { executeActionDelegate });
                }
                catch { }
            });
        }
    }
}